#pragma once

#include <bits/stdc++.h>
#include <boost/multiprecision/cpp_int.hpp>
#include <nanobdd/common.h>
#include <nanobdd/node.h>
#include <map>
#include <optional>
#include <tuple>
#include <vector>
#include <boost/unordered_map.hpp>

namespace nanobdd {

class MapCache {
 public:
  MapCache() = default;
  MapCache(size_t size) : size_(size) {}

  void
  insert(
      std::shared_ptr<Node> node,
      uint32_t op,
      std::shared_ptr<Node> left,
      std::shared_ptr<Node> right) {
    // auto key = std::make_tuple(op, left.get(), right.get());
    auto key = boost::multiprecision::uint256_t(op) << 128 |
    boost::multiprecision::uint256_t((uint64_t)left.get()) << 64 |
    boost::multiprecision::uint256_t((uint64_t)right.get());
    cache_[key] = node;
    // cache_.emplace(std::make_tuple(op, left.get(), right.get()), node);
  }

  std::optional<std::shared_ptr<Node>>
  lookup(uint32_t op, std::shared_ptr<Node> left, std::shared_ptr<Node> right) {
    // auto key = std::make_tuple(op, left.get(), right.get());
    auto key = boost::multiprecision::uint256_t(op) << 128 |
    boost::multiprecision::uint256_t((uint64_t)left.get()) << 64 |
    boost::multiprecision::uint256_t((uint64_t)right.get());
    auto iter = cache_.find(key);
    if (iter != cache_.end()) {
      return iter->second;
    }
    return std::nullopt;
  }

  void
  resize(size_t size) {
    size_ = size;
  }

  size_t
  size() {
    return size_;
  }

  auto&
  cache() {
    return cache_;
  }

 private:
  uint32_t size_;
  // std::unordered_map<
  //     std::tuple<
  //         uint32_t /* op */,
  //         Node* /* left node ptr */,
  //         Node* /* right node ptr */>,
  //     std::shared_ptr<Node>>
  //     cache_;
  boost::unordered_map<boost::multiprecision::uint256_t, std::shared_ptr<Node>> cache_;
};

} // namespace nanobdd